<?php
namespace App\Libs\Pay;

use App\Libs\Base\Config;
use App\Libs\Remote\Remote;
use \App\Libs\Views\Json;



class PayLibs extends Event
{
    protected $closureMap;
    protected static $copyClosureMap;
    protected static $aopClientObj;
    protected static $alipayConfig;
    protected static $weixinPayConfig;
    public static function attach($event,\Closure $closure){
        if(!isset(self::$copyClosureMap[$event])){
            self::$copyClosureMap[$event] = $closure;
        }else{
            throw new \Exception('重复注册事件');
        }
    }

    public static function detach($event){
        if(isset(self::$copyClosureMap[$event])) {
            unset(self::$copyClosureMap[$event]);
        }
    }

    public static function trigger($event,$args){
        return call_user_func_array( self::$copyClosureMap[$event] , $args);
    }
    /**
     * PayLibs constructor.
     * @param bool $check 是否是验证
     */
    private static function alipayConstruct($check = false)
    {
        require __DIR__ . '/Alipay/AopSdk.php';
        self::$aopClientObj = new \AopClient();
        //支付参数
        self::$alipayConfig = Config::instance()->pay['alipay'];
        if (!$check) {
            self::$aopClientObj->appId = self::$alipayConfig['appId'];
            self::$aopClientObj->rsaPrivateKey = self::$alipayConfig['rsaPrivateKey'];
            self::$aopClientObj->signType = self::$alipayConfig['signType'];
        }
        self::$aopClientObj->alipayrsaPublicKey = self::$alipayConfig['alipayrsaPublicKey'];
    }

    /**
     * 初始化微信支付；
     */
    private static function weixinConstruct(){
        self::$weixinPayConfig = Config::instance()->pay['weixin'];
    }

    /*
     * 初始化公众号支付
     */
    private static function weixinH5Construct(){
        self::$weixinPayConfig = Config::instance()->pay['weixin_gzh'];
    }
    /**
     * @param $pay_type   alipayApp | alipayH5 | weixinApp
     * @param $orderSN
     * @param $goodsDesc  商品描述
     * @param $totalPrice  总数：元
     * @param null $notifyUrl 微信通知url
     * @param string $attach 附属数据;
     * @return array
     */
    public static function payAll($pay_type,$orderSN,$goodsDesc,$totalPrice,$notifyUrl=null,$attach='buy'){
        $goodsInfo = [
            'subject' => $goodsDesc,
            'out_trade_no' => $orderSN,
            'total_amount' => $totalPrice,   //订单总金额
            'product_code' => 'QUICK_MSECURITY_PAY',   //固定值\
            'passback_params' => urlencode(json_encode([
                'type' => $attach
            ])),
            //------
            'total_fee' => $totalPrice*100,
            'notify_url' => $notifyUrl
        ];
        switch ($pay_type){
            case 'alipayApp':
                $res = self::alipayGetAppStr($goodsInfo);
                return $res;
                break;
            case 'alipayH5' :
                $res = self::alipayGetH5Str($goodsInfo);
                return $res;
                break;
            case 'weixinApp':
                $res = self::weixinPay('APP',$goodsInfo['out_trade_no'],$goodsInfo['subject'],$goodsInfo['total_fee'],$goodsInfo['notify_url'],$goodsInfo['passback_params']);
                return $res;
                break;
            case 'weixinH5':
                $res = self::weixinPay('H5',$goodsInfo['out_trade_no'],$goodsInfo['subject'],$goodsInfo['total_fee'],$goodsInfo['notify_url'],$goodsInfo['passback_params']);
                return $res;
                break;
        }
    }

    /**
     * @param $type  weixin |  alipay
     * @param $orderSN
     * @param $refundTotal  退款金额：元；
     * @return array
     */
    public static function refundAll($type,$orderSN,$refundTotal){
        $refundInfo = [
                'out_trade_no'=> $orderSN,//(本系统订单号),
                'refund_amount'=> $refundTotal,//（退款金额）单位元
                'trade_no'=>'',//（支付宝交易号）

        ];
        switch($type){
            case 'weixin':
                $res = self::weixinRefund($refundInfo['out_trade_no'],$refundInfo['refund_amount']);
                return $res;
                break;
            case 'alipay':
                $response = self::alipayRefund($refundInfo);
                return $response;
                break;
        }
    }
    /**
     * @param $goodsInfo   商品描述信息;subject，out_trade_no,total_amount,product_code(必须)
     * @return array
     */
    public static function alipayGetAppStr($goodsInfo)
    {
        self::alipayConstruct();
        $createReq = new \AlipayTradeAppPayRequest();
        $createReq->setNotifyUrl(self::$alipayConfig['notify_url']);
        $createReq->setBizContent(json_encode($goodsInfo));
        $queryString = self::$aopClientObj->sdkExecute($createReq);
        return [
            'alipayStr' => $queryString
        ];
    }

    public static function alipayGetH5Str($goodsInfo){
        self::alipayConstruct();
        $createReq = new \AlipayTradeWapPayRequest();
        $createReq->setNotifyUrl(self::$alipayConfig['notify_url']);
        $createReq->setBizContent(json_encode($goodsInfo));
        $jumpUrl = self::$aopClientObj->pageExecute($createReq,'GET');
       return $jumpUrl;
    }


    /*  //所有支付类型;
        name：
     * BuyTicket:买票;
     * ChargeCredit:冲积分;
     * ToBeMember  :充会员;
     * purchaseLessions：买课程
     * //第三步检测：name+CheckAlipay
     * success:  name+SuccessAlipay
     *
     */
    public static function alipayCheckResult($post)
    {

       self::alipayConstruct(true);
        $flag = self::$aopClientObj->rsaCheckV1($post, NULL, self::$alipayConfig['signType']);  //验证签名
        if ($flag) {
            //验证支付逻辑
            $data['out_trade_no'] = $post['out_trade_no'];
            $data['alipay_trade_no']= $post['trade_no'];
            $data['alipay_pay_time']= $post['gmt_payment'];
            //交易状态：字符串
            $data['trade_status'] = $post['trade_status'];
            //交易额
            $data['total_amount'] = $post['total_amount'];
            $data['pass_params'] = json_decode(urldecode($post['passback_params']));
            $handelName = $data['pass_params'];
            $appId = $post['app_id'];

            //1 ：验证appid
            if (self::$alipayConfig['appId'] != $appId) {
                echo 'failure';
                die;
            }
            //2 ： 验证seller_email
            if (self::$alipayConfig['seller_email'] != $post['seller_email']) {
                echo 'failure';
                die;
            }
            //3 : 验证是否有此订单号码
            /*
            if ($data['pass_params']->type == 'chargeVIP') {
                //验证充值会员的订单；验证金额对不对;

            } elseif ($data['pass_params']->type == 'purchaseLessions') {
                //验证购买订单是否存在，金额是否正确;
            }
            */
            //此处触发：
            //purchaseLessions
            //
            $res = self::trigger($handelName.'CheckAlipay',[$data]);
            if(!$res){
                echo 'failure';
                die;
            }

            //官方文档4步验证完成，所有数据校验已经完成
            if ($data['trade_status'] == 'TRADE_SUCCESS' || $data['trade_status'] == 'TRADE_FINISHED') {
                /*
                if ($data['pass_params']->type == 'chargeVIP') {

                     //根据订单状态检测重复通知；
                     //保存返回信息
                } elseif ($data['pass_params']->type == 'purchaseLessions') {

                     //确定订单状态；防止重复通知；
                     //保存通知数据；
                     //修改订单状态；
                     //增加销量；
                }
                */
                $res = self::trigger($handelName.'SuccessAlipay',[$data]);
                if(!$res){
                    echo 'failure';
                    die;
                }

            }elseif($data['trade_status'] == 'TRADE_CLOSED') {
                //交易关闭未做
                echo 'success';
                die;
            }
        } else {
            //验签失败
            echo 'failure';
            die;
        }
    }


    /*
     *  array $refundInfo 数组：至少包含订单号和退款金额:
     *                      out_trade_no(本系统订单号),
     *                      refund_amount（退款金额）
     *                      trade_no（支付宝交易号）
     * array|\SimpleXMLElement[]
     */
    public static function alipayRefund($refundInfo = [])
    {
        if(!isset(self::$copyClosureMap['handleAlipayRefund'])){
            throw new \Exception('事件(handleAlipayRefund)未注册');
        }
        self::alipayConstruct();
        $refundRequest = new \AlipayTradeRefundRequest();
        $refundRequest->setBizContent(json_encode($refundInfo));
        $result = self::$aopClientObj->execute($refundRequest);
        $responseNode = str_replace(".", "_", $refundRequest->getApiMethodName()) . "_response";
        $resultCode = $result->$responseNode->code;
        if (!empty($resultCode) && $resultCode == 10000) {
            //成功
            self::trigger('handleAlipayRefund',[$result->$responseNode]);
            return ['status' => 'success','response'=> $result->$responseNode];
        } else {
            //失败
            if (isset($result->$responseNode->sub_msg)) {
                $returnMsg = $result->$responseNode->sub_msg;
            } else {
                $returnMsg = $result->$responseNode->msg;
            }
            return ['status'=> 'fail', 'msg' =>'失败:'.$resultCode.','.$returnMsg];
        }
    }


        //获取：返回支付标识，然后调起客户端进行支付；

    /**
     * @param $type 支付类型：APP,H5
     * @param $order_sn  本系统订单号
     * @param $goodsDesc  格式： APP名称+目的；
     * @param $totalFee  总金额：分
     * @param string $attach  自定义数据;
     * @param string $notifyUrl 通知地址；
     * @return bool
     */
    protected static function weixinGetPrePayID($type,$order_sn,  $goodsDesc, $totalFee,$notifyUrl=null,$attach='buy'){

            switch ($type){
                case 'APP':
                    self::weixinConstruct();
                    $params = array(
                        'appid' => self::$weixinPayConfig['open_appid'],   //微信开放平台审核通过的应用APPID
                        'mch_id' => self::$weixinPayConfig['mch_id'],
                        'device_info' => 'WEB',
                        'nonce_str' => self::getGenerate(mt_rand(20,32)),
                        'body' => $goodsDesc,
                        'trade_type' => 'APP'
                    );
                    break;
                case 'H5':
                    self::weixinH5Construct();
                    $params = array(
                        'appid' => self::$weixinPayConfig['open_appid'],   //微信开放平台审核通过的应用APPID
                        'mch_id' => self::$weixinPayConfig['mch_id'],
                        'device_info' => 'WEB',
                        'nonce_str' => self::getGenerate(mt_rand(20,32)),
                        'body' => $goodsDesc,
                        'trade_type' => 'JSAPI'
                    );
                    break;

            }
            //删除了.date('His')
            $params['out_trade_no'] = $order_sn;
            $params['total_fee'] = $totalFee*100;
            $params['spbill_create_ip'] = $_SERVER['REMOTE_ADDR'];
            $params['time_start'] = date('YmdHis');
            $params['time_expire'] = date('YmdHis', time() + 1800);
            $params['notify_url'] =$notifyUrl ?? 'https://'.$_SERVER['HTTP_HOST'].'/notify/notifyAll' ;
            //增加了type
            $params['attach'] = $attach;

            ksort($params);
            $str = urldecode(http_build_query($params));
            $str .= '&key=' . self::$weixinPayConfig['sign_key'];
            $params['sign'] = strtoupper(md5($str));
            $paramsXml = self::arrayToXml($params);

            //发送支付请求;
            $response = Remote::request('wx_pay', 'pay/unifiedorder', $paramsXml);
            $xmlOBj = simplexml_load_string($response['body'],'SimpleXMLElement',LIBXML_NOCDATA);
            $resArray = self::xmlToArray($xmlOBj);
            if (!empty($resArray) && $resArray['return_code'] === 'SUCCESS' && $resArray['result_code'] === 'SUCCESS') {
                return  $resArray['prepay_id'];
            }else if($resArray['return_code'] === 'SUCCESS' && $resArray['result_code'] === 'FAIL' ){
                return ['err_code' =>$resArray['err_code'],'err_code_des'=> $resArray['err_code_des']];
            }else{
                return ['err_code' =>$resArray['return_code'],'err_code_des'=> $resArray['return_msg']];
            }

    }
    /**
     * @param $type APP || H5
     * @param $order_sn
     * @param $goodsDesc
     * @param $totalFee
     * @param null $notifyUrl
     * @param string $attach
     * @return array
     */
    public static function weixinPay($type,$order_sn,  $goodsDesc, $totalFee,$notifyUrl=null,$attach='buy'){
        $prePayID = self::weixinGetPrePayID($type,$order_sn,$goodsDesc,$totalFee,$notifyUrl,$attach);
        if(is_array($prePayID)){
            return Json::view(1, implode(',',$prePayID), null);
        }
        $nonceStr = self::getGenerate(mt_rand(20,32));
        switch ($type){
            case 'APP':
                $payPullStr = array(
                    'appid' => self::$weixinPayConfig['open_appid'],   //微信开放平台审核通过的应用APPID
                    'partnerid' => self::$weixinPayConfig['mch_id'],
                    'timestamp' => time(),
                    'noncestr' => $nonceStr,
                    'prepayid' => $prePayID,
                    'package' => 'Sign=WXPay'
                );
                ksort($payPullStr);
                $str = urldecode(http_build_query($payPullStr));
                $str .= '&key=' . self::$weixinPayConfig['sign_key'];
                $payPullStr['sign'] = strtoupper(md5($str));
                return $payPullStr;
                break;

            case 'H5':
                $payPullStr = array(
                    'appId' => self::$weixinPayConfig['open_appid'],   //微信开放平台审核通过的应用APPID
                    'timeStamp' => time(),
                    'nonceStr' => $nonceStr,
                    'package' => 'prepay_id='.$prePayID,
                    'signType' => 'MD5',
                );
                ksort($payPullStr);
                $str = urldecode(http_build_query($payPullStr));
                $str .= '&key=' . self::$weixinPayConfig['sign_key'];
                $payPullStr['paySign'] = strtoupper(md5($str));
                return $payPullStr;
                break;
        }
    }

    /**
     * @param $orderSN  订单号
     * @param $refundTotal  退款金额：单位 ：元
     * @return array|\simpleXmlElement
     */
    private static function weixinRefundPre($orderSN,$refundTotal){
        self::weixinConstruct();
        $params = array(
            'appid' => self::$weixinPayConfig['open_appid'],   //微信开放平台审核通过的应用APPID
            'mch_id' => self::$weixinPayConfig['mch_id'],
            'nonce_str' => self::getGenerate(mt_rand(20, 32)),
            'out_trade_no' => $orderSN,
            'out_refund_no' => $orderSN,
            //订单金额;
            'total_fee' => $refundTotal*100,
            'refund_fee' => $refundTotal*100,
        );
        ksort($params);
        $str = urldecode(http_build_query($params));
        $str .= '&key=' . self::$weixinPayConfig['sign_key'];
        $params['sign'] = strtoupper(md5($str));
        $xmlParams = self::arrayToXml($params);
        $data = self::weixinRefundPostCert($xmlParams);
        if (!$data) {
            return Json::view(1, '请求发送失败', []);
        }

//        <xml><return_code><![CDATA[SUCCESS]]></return_code>
//<return_msg><![CDATA[OK]]></return_msg>
//<appid><![CDATA[wx2685e590bd08501b]]></appid>
//<mch_id><![CDATA[1488937032]]></mch_id>
//<nonce_str><![CDATA[NCyD6o1TG9j2rY9C]]></nonce_str>
//<sign><![CDATA[A4D71A38A339975C2CE9A01E08EBD6E3]]></sign>
//<result_code><![CDATA[FAIL]]></result_code>
//<err_code><![CDATA[NOTENOUGH]]></err_code>
//<err_code_des><![CDATA[基本账户余额不足，请充值后重新发起]]></err_code_des>
//</xml>
        $sourceData = self::xmlToArrayStr($data);
        return $sourceData;
    }

    private static function weixinRefund($orderSN,$totalFee){

        if(!isset(self::$copyClosureMap['handleWxRefund'])){
            throw new \Exception('事件(handleWxRefund)未注册');
        }
        self::weixinConstruct();
        $returnData = self::weixinRefundPre($orderSN,$totalFee);
        if($returnData['return_code'] == 'SUCCESS' && $returnData['result_code'] == 'FAIL'){
            return ['status' => 'fail','msg' => '失败：'.implode(',',[$returnData['err_code'],$returnData['err_code_des']])];
        }elseif ($returnData['return_code'] == 'SUCCESS' && $returnData['result_code'] == 'SUCCESS'){
            self::trigger('handleWxRefund',[$returnData]);
            return ['status' => 'success','response' => $returnData];
        }else{
            return ['status' => 'fail','msg' => '失败原因需要进一步排查'];
        }
    }

    /**
     * @param $sourceData   微信返回的数组数据;
     * @return bool
     * @throws \Exception
     */

    /*  //所有支付类型;
        name：
     * BuyTicket:买票;
     * ChargeCredit:冲积分;
     * ToBeMember  :充会员;
     * purchaseLessions：买课程
     * //第三步检测：name+HybridProcess
     *
     */
    public static function wxCheckResult($sourceData){
        self::weixinConstruct();
        $data = $sourceData;
        $sign = $data['sign'];
        $handleName = json_decode(urldecode($data['attach']['passback_params']));
        unset($data['sign']);
        ksort($data);
        $str = http_build_query($data);
        $str .= '&key=' . self::$weixinPayConfig['sign_key'];
        $md5 = strtoupper(md5($str));
        if ($sign !== $md5) {
            $ret = array('return_code' => 'FAIL', 'return_msg' => '签名失败');
            $xmlResponse = self::arrayToXml($ret);
            echo $xmlResponse;
        }
        //校验成功之后所有返回都应该是成功；
        /*
        switch ($type){
            case 'purchaseLessions':
//                 * 验证订单是否存在；
//                 * 价格
//                 * 订单状态；屏蔽；
//                 * 改变订单状态；纪录支付方式；
//                 * 增加销量；
                break;
            case 'chargeVIP':
//                 * 验证订单存在
//                 * 验证金额；
//                 * 多册屏蔽；
//                 * 改变用户的状态;
        }
        */
        $res = self::trigger($handleName.'HybridProcess',[$data]);
        if($res){
            $ret = array('return_code' => 'SUCCESS', 'return_msg' => 'OK');
            $xmlResponse = self::arrayToXml($ret);
            echo $xmlResponse;
        }

    }
    private static function weixinRefundPostCert($xmlparams)
    {
        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);//证书检查
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
        curl_setopt($ch, CURLOPT_SSLCERT,APP_PATH . '/app/configs'.DIRECTORY_SEPARATOR.'Cert'.DIRECTORY_SEPARATOR.'apiclient_cert.pem');
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
        curl_setopt($ch, CURLOPT_SSLKEY,APP_PATH . '/app/configs'.DIRECTORY_SEPARATOR.'Cert'.DIRECTORY_SEPARATOR. 'apiclient_key.pem');
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
        curl_setopt($ch, CURLOPT_CAINFO,APP_PATH . '/app/configs'.DIRECTORY_SEPARATOR.'Cert'.DIRECTORY_SEPARATOR. 'rootca.pem');
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlparams);

        $data = curl_exec($ch);
        if ($data) { //返回来的是xml格式需要转换成数组再提取值，用来做更新
            return $data;
        } else {
            return false;
        }
    }
    private static function weixinValidate($sourceData)
    {
        $sign = $sourceData['sign'];
        unset($sourceData['sign']);
        ksort($sourceData);
        $str = http_build_query($sourceData);
        $str .= '&key=' . self::$weixinPayConfig['sign_key'];
        $md5 = strtoupper(md5($str));
        if ($sign == $md5) {
            return $sourceData;
        } else {
            return false;
        }
    }

    public static function logNotifyInfo($payType,$data,$logFileName){

        $msgbefore ="
########################################################
系统收到一笔{$payType}订单通知，详细信息如下：
";
        $afterMsg='
########################################################
';
        $msg = $msgbefore . var_export($data, true).$afterMsg;
        Logger::info($logFileName, $msg);
    }
    public static function log($type,$paytype,$errorMsg,$data,$logFileName){
        $msgbefore ="
########################################################
类型：{$type}                支付方式：{$paytype}     
错误信息：{$errorMsg}  
";
        $afterMsg='
########################################################
';
        $msg = $msgbefore . var_export($data, true).$afterMsg;
        Logger::info($logFileName, $msg);
    }
    public static function getGenerate( $length = 8 ) {
        // 密码字符集，可任意添加你需要的字符
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $password = '';
        for ( $i = 0; $i < $length; $i++ )
        {
            $password .= $chars[ mt_rand(0, strlen($chars) - 1) ];
        }
        return $password;
    }
    public static function arrayToXml($arr)
    {
        $xml = "<xml>";
        /*$xml = '<?xml version="1.0" encoding="UTF-8"?>';*/
        foreach ($arr as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    public static function xmlToArray(\simpleXmlElement $simpleXmlElement){
        $simpleXmlElement=(array)$simpleXmlElement;
        foreach($simpleXmlElement as $k=>$v){
            if($v instanceof SimpleXMLElement ||is_array($v)){
                $simpleXmlElement[$k]=xmlToArray($v);
            }
        }
        return $simpleXmlElement;
    }
    public static function xmlToArrayStr( $xmlStr){
        $xmlOBj = simplexml_load_string($xmlStr,'SimpleXMLElement',LIBXML_NOCDATA);
        $resArray = self::xmlToArray($xmlOBj);
        return $resArray;
    }
}

